import sys
import time
import traceback
from typing import Literal

from Tea.exceptions import TeaException
# noinspection PyUnresolvedReferences
from alibabacloud_ecs20140526.client import Client as EcsClient
# noinspection PyUnresolvedReferences
from alibabacloud_ecs20140526.models import DeleteInstanceRequest, DescribeInstancesRequest, \
    DescribeInstancesResponseBodyInstancesInstance, RunInstancesRequest


def run_instance(
    client: EcsClient,
    request_dict: dict,
    instance_name: str | None = None,
) -> str:
    if instance_name is not None:
        request_dict['InstanceName'] = instance_name

    request = RunInstancesRequest().from_map(request_dict)

    response = client.run_instances(request)

    return response.body.instance_id_sets.instance_id_set[0]


def describe_instance(
    client: EcsClient,
    instance_id: str,
) -> DescribeInstancesResponseBodyInstancesInstance:
    request = DescribeInstancesRequest()
    request.region_id = client._region_id
    request.instance_ids = f'["{instance_id}"]'

    response = client.describe_instances(request)

    instances = response.body.instances.instance
    return instances[0] if instances else None


def get_instance_status(
    client: EcsClient,
    instance_id: str,
) -> Literal['Pending', 'Running', 'Starting', 'Stopping', 'Stopped'] | None:
    instance = describe_instance(client, instance_id)
    return instance.status if instance else None


def delete_instance(
    client: EcsClient,
    instance_id: str, *,
    initial_delay: float = 3,
    retry_interval: float = 3,
    init_interval: float = 30,
):
    request = DeleteInstanceRequest()
    request.instance_id = instance_id
    request.force = True

    print(f"Terminating instance {instance_id}...", file=sys.stderr)
    time.sleep(initial_delay)
    while True:
        instance_state = get_instance_status(client, instance_id)
        if instance_state is None:
            print(f"Instance terminated.", file=sys.stderr)
            return

        try:
            client.delete_instance(request)
        except TeaException as err:
            if err.code == 'IncorrectInstanceStatus.Initializing':
                print(f"Instance is initializing, waiting...", file=sys.stderr)
                time.sleep(init_interval)
                continue
            traceback.print_exc(file=sys.stderr)
            print(f"Retrying after {retry_interval}s", file=sys.stderr)
            time.sleep(retry_interval)
            print("Retrying...", file=sys.stderr)


def get_instance_ip_or_none(
    client: EcsClient,
    instance_id: str, *,
    network: Literal['public', 'inner']
) -> str | None:
    instance = describe_instance(client, instance_id)
    if not instance:
        return None

    if network == 'public':
        ip_list = instance.public_ip_address.ip_address
    elif network == 'inner':
        ip_list = instance.inner_ip_address.ip_address
    else:
        raise ValueError(f"Invalid network {network=}")

    return ip_list[0] if ip_list else None


def get_instance_ip_or_retry(
    client: EcsClient,
    instance_id: str, *,
    network: Literal['public', 'private'],
    initial_delay: float = 3,
    retry_interval: float = 3
) -> str:
    ip = None
    time.sleep(initial_delay)
    while ip is None:
        time.sleep(retry_interval)
        ip = get_instance_ip_or_none(client, instance_id, network=network)
    return ip
